//
//  QSFetchModel.m
//  QuicklyShop
//
//  Created by 梵天 on 14-7-15.
//  Copyright (c) 2014年 com. All rights reserved.
//

#import <objc/runtime.h>
#import "QSFetchModel.h"
#import "QSFetchModelProperty.h"
#import "AppVersionUtil.h"


#pragma mark - NSArray+QSFetchModel

@interface NSArray (QSFetchModel)

- (NSArray *)modelArrayWithClass:(Class)modelClass;

@end

@implementation NSArray (QSFetchModel)

- (NSArray *)modelArrayWithClass:(Class)modelClass{
    NSMutableArray *modelArray = [NSMutableArray array];
    for (id object in self) {
        if ([object isKindOfClass:[NSArray class]]) {
            [modelArray addObject:[object modelArrayWithClass:modelClass]];
        } else if ([object isKindOfClass:[NSDictionary class]]){
            [modelArray addObject:[[modelClass alloc] initWithJSONDict:object]];
        } else {
            [modelArray addObject:object];
        }
    }
    return modelArray;
}
@end

#pragma mark - NSDictionary+QSFetchModel

@interface NSDictionary (QSFetchModel)

- (NSDictionary *)modelDictionaryWithClass:(Class)modelClass;

@end

@implementation NSDictionary (QSFetchModel)

- (NSDictionary *)modelDictionaryWithClass:(Class)modelClass{
    NSMutableDictionary *modelDictionary = [NSMutableDictionary dictionary];
    for (NSString *key in self) {
        id object = [self objectForKey:key];
        if ([object isKindOfClass:[NSDictionary class]]) {
            [modelDictionary setObject:[[modelClass alloc] initWithJSONDict:object] forKey:key];
        }else if ([object isKindOfClass:[NSArray class]]){
            [modelDictionary setObject:[object modelArrayWithClass:modelClass] forKey:key];
        }else{
            [modelDictionary setObject:object forKey:key];
        }
    }
    return modelDictionary;
}

@end

#pragma mark - QSFetchModel

static const char *QSFecthModelKeyMapperKey;
static const char *QSFetchModelPropertiesKey;

@interface QSFetchModel()
{
    AFHTTPRequestOperation *requestOperation;
}

@property(nonatomic, strong) AFHTTPRequestOperationManager *operationManager;

- (void)setupCachedKeyMapper;
- (void)setupCachedProperties;

@end

@implementation QSFetchModel

+ (NSString *)customUserAgent{
    
    NSString *channel = ([[AppVersionUtil buildVersion] intValue] % 2) ? @"TaoWan" : @"appStore";
    
    NSString *deviceName = @"iPhone";
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        deviceName = @"iPad";
    }
    
    NSString *header_info = [NSString stringWithFormat:@"channel:%@, client:%@", channel, deviceName];
    
    return header_info;
    
}

//+ (void)resetCookie {
//    NSArray *cookies = [NSKeyedUnarchiver unarchiveObjectWithData:[USERDEFAULTS objectForKey:kSystemCookie]];
//    if (cookies) {
//        for (NSHTTPCookie *httpCookie in cookies) {
//            [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:httpCookie];
//        }
//    }
//}

- (instancetype)init{
    
    self = [super init];
    if (self) {
        
        self.enableResetInjectData=YES;
        [self setupCachedKeyMapper];
        [self setupCachedProperties];
    }
    return self;
}

- (instancetype)initWithJSONDict:(NSDictionary *)dict{

    self = [self init];
    if (self) {
        [self injectJSONData:dict];
    }
    return self;
}

- (void)dealloc{
    
    if (requestOperation) {
        [requestOperation cancel];
    }
}

- (AFHTTPRequestOperationManager *)operationManager{
    
    if (!_operationManager) {
        NSURL *baseURL = [NSURL URLWithString:[self baseURLString]];
        _operationManager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL];
        _operationManager.requestSerializer.timeoutInterval = 20;
        AFHTTPRequestSerializer *reqSerializer = _operationManager.requestSerializer;
        [reqSerializer setValue:[[self class] customUserAgent] forHTTPHeaderField:@"User-Agent"]
        ;
    }
    
    return _operationManager;
}

- (AFHTTPRequestOperation *)HTTPRequestOperationWithHTTPMethod:(NSString *)method
                                                     URLString:(NSString *)URLString
                                                    parameters:(id)parameters
                                                       success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                                                       failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self.operationManager.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.operationManager.baseURL] absoluteString] parameters:parameters error:&serializationError];
    
    return [self.operationManager HTTPRequestOperationWithRequest:request success:success failure:failure];
}

typedef void (^afsuccessBlock)(AFHTTPRequestOperation *operation, id responseObject);
typedef void (^affailureBlock)(AFHTTPRequestOperation *operation, NSError *error);


- (void)synfetchWithPath:(NSString *)path completionHandler:(FetchCompletionHandler)handler{
    __weak typeof(self) weakSelf = self;

    afsuccessBlock successBlock=^(AFHTTPRequestOperation *operation, id responseObject){
        if (!weakSelf) {
            return;
        }
        
#ifdef DEBUG
        NSLog(@"RESPONSE JSON:%@", responseObject);
#endif
        
        NSNumber *errorCode = [responseObject objectForKey:@"errorCode"];
        id data = [responseObject objectForKey:@"data"];
        if (errorCode.integerValue == 0) {
            
            [weakSelf injectJSONData:data];
            handler(YES, nil);
        }else{
            NSString *errorInfo = [responseObject objectForKey:@"errorInfo"];
            NSDictionary *dict = @{NSLocalizedDescriptionKey: errorInfo ?: @"req error..."};
            NSError *bizError = [NSError errorWithDomain:QSBizErrorDomain
                                                    code:errorCode.integerValue
                                                userInfo:dict];
            handler(NO, bizError);
        }
    };
    
    affailureBlock failureBlock=^(AFHTTPRequestOperation *operation, NSError *error){
        if (!weakSelf) {
            return;
        }
        
#ifdef DEBUG
        NSString *reqUrl = [operation.request.URL absoluteString];
        NSString *params = [[NSString alloc] initWithData:operation.request.HTTPBody
                                                 encoding:NSUTF8StringEncoding];
        NSLog(@"FAILURE URL:%@ \nPARAMS:%@ \nAND RESPONSE:%@", reqUrl, params, operation.response);
#endif
        handler(NO, error);
    };

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [requestOperation cancel];
        requestOperation = [self HTTPRequestOperationWithHTTPMethod:@"POST" URLString:[NSString stringWithFormat:@"%@%@", @"", path]
                                                         parameters:_requestParams
                                                            success:nil
                                                            failure:nil];
        
        [requestOperation start];
        [requestOperation waitUntilFinished];
        
        NSError* error=[requestOperation error];
        if(error)
        {
            failureBlock(requestOperation,error);
        }
        else
        {
            id obj=[requestOperation responseObject];
            successBlock(requestOperation,obj);
        }
    });
    
    

}


- (void)fetchWithPath:(NSString *)path completionHandler:(FetchCompletionHandler)handler NoProgress:(BOOL)noProgress{
    
    if (!noProgress) {
        [[LBToastManager manager] showprogress:nil];
    }
    
    __weak typeof(self) weakSelf = self;
    [requestOperation cancel];
    requestOperation = [self.operationManager POST:[NSString stringWithFormat:@"%@%@", @"", path]
                                        parameters:_requestParams
                                           success:^(AFHTTPRequestOperation *operation, id responseObject){
                                               [[LBToastManager manager] hideprogress]; // hide progress
                                               
                                               if (!weakSelf) {
                                                   return;
                                               }
                                               
#ifdef DEBUG
                                               NSLog(@"RESPONSE JSON:%@", responseObject);
#endif
                                               
                                               NSNumber *errorCode = [responseObject objectForKey:@"errorCode"];
                                               
                                               id data = [responseObject objectForKey:@"data"];
                                               if (errorCode.integerValue == 0) {
                                                   [weakSelf injectJSONData:data];
                                                   handler(YES, nil);
                                               }else{
                                                   NSString *errorInfo = [responseObject objectForKey:@"errorInfo"];
                                                   NSDictionary *dict = @{NSLocalizedDescriptionKey: errorInfo ?: @"req error..."};
                                                   NSError *bizError = [NSError errorWithDomain:QSBizErrorDomain
                                                                                           code:errorCode.integerValue
                                                                                       userInfo:dict];
                                                   [[LBToastManager manager] showtoast:errorInfo?:@"操作失败"];
                                                   handler(NO, bizError);
                                               }
                                           }
                                           failure:^(AFHTTPRequestOperation *operation, NSError *error){
                                                [[LBToastManager manager] hideprogress]; // hide progress
                                               
                                               if (!weakSelf) {
                                                   return;
                                               }
                                               
                                               if (error.code == -1004) {
                                                   [[LBToastManager manager] showtoast:@"亲，您的网络出问题了"];
                                               }
                                               else if(error.code == -1011){
                                                   [[LBToastManager manager] showtoast:@"获取数据失败"];
                                               }
                                               
#ifdef DEBUG
                                               NSString *reqUrl = [operation.request.URL absoluteString];
                                               NSString *params = [[NSString alloc] initWithData:operation.request.HTTPBody
                                                                                        encoding:NSUTF8StringEncoding];
                                               NSLog(@"FAILURE URL:%@ \nPARAMS:%@ \nAND RESPONSE:%@", reqUrl, params, operation.response);
#endif
                                               handler(NO, error);
                                           }];
}

- (void)uploadImageWithPath:(NSString *)path imageData:(NSData *)imgData completionHandler:(FetchCompletionHandler)handler {
    
    [[LBToastManager manager] showprogress:nil];
    __weak typeof(self) weakSelf = self;
    [requestOperation cancel];
    requestOperation = [self.operationManager POST:path parameters:_requestParams constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
        [formData appendPartWithFileData:imgData name:@"file" fileName:@"file[0]" mimeType:@"image/jpeg"];
    } success:^(AFHTTPRequestOperation *operation, id responseObject) {
        [[LBToastManager manager] hideprogress];
        [weakSelf injectJSONData:responseObject];
        NSLog(@"response:%@", responseObject);
        handler(YES, nil);
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        [[LBToastManager manager] hideprogress];
        NSLog(@"error:%@", error);
    }];
}

- (void)uploadFileWithPath:(NSString *)path filePath:(NSString *)filePath completionHandler:(FetchCompletionHandler)handler {
    [[LBToastManager manager] showprogress:nil];
    __weak typeof(self) weakSelf = self;
    [requestOperation cancel];
    requestOperation = [self.operationManager POST:path parameters:_requestParams constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
        
        [formData appendPartWithFileURL:[NSURL fileURLWithPath:filePath] name:@"attachment" error:nil];
        
    } success:^(AFHTTPRequestOperation *operation, id responseObject) {
        [[LBToastManager manager] hideprogress];
        [weakSelf injectJSONData:responseObject];
        NSLog(@"response:%@", responseObject);
        handler(YES, nil);
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        [[LBToastManager manager] hideprogress];
        NSLog(@"error:%@", error);
    }];
}

#pragma mark - QSFetchModel Configuration

- (void)setupCachedKeyMapper{
    
    if (objc_getAssociatedObject(self.class, &QSFecthModelKeyMapperKey) == nil) {
        
        NSDictionary *dict = [self modelKeyJSONKeyMapper];
        if (dict.count) {
            objc_setAssociatedObject(self.class, &QSFecthModelKeyMapperKey, dict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    }
}

- (void)setupCachedProperties{
    
    if (objc_getAssociatedObject(self.class, &QSFetchModelPropertiesKey) == nil) {
        
        NSMutableDictionary *propertyMap = [NSMutableDictionary dictionary];
        Class class = [self class];
        
        while (class != [QSFetchModel class]) {
            unsigned int propertyCount;
            objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
            for (unsigned int i = 0; i < propertyCount; i++) {
                
                objc_property_t property = properties[i];
                const char *propertyName = property_getName(property);
                NSString *name = [NSString stringWithUTF8String:propertyName];
                const char *propertyAttrs = property_getAttributes(property);
                NSString *typeString = [NSString stringWithUTF8String:propertyAttrs];
                QSFetchModelProperty *modelProperty = [[QSFetchModelProperty alloc] initWithName:name typeString:typeString];
                if (!modelProperty.isReadonly) {
                    [propertyMap setValue:modelProperty forKey:modelProperty.name];
                }
            }
            free(properties);
            
            class = [class superclass];
        }
        objc_setAssociatedObject(self.class, &QSFetchModelPropertiesKey, propertyMap, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}

- (NSDictionary *)modelKeyJSONKeyMapper{
    return @{};
}

- (NSString *)baseURLString{
    if (self.customBaseUrl) {
        return self.customBaseUrl;
    }
    else{
        return [NSString stringWithFormat:@"%@:80", BASE_URL];
    }
}

#pragma mark - QSFetchModel Runtime Injection

- (void)injectJSONData:(id)dataObject{
    
    NSDictionary *keyMapper = objc_getAssociatedObject(self.class, &QSFecthModelKeyMapperKey);
    NSDictionary *properties = objc_getAssociatedObject(self.class, &QSFetchModelPropertiesKey);
    
    if ([dataObject isKindOfClass:[NSArray class]]) {
        
        QSFetchModelProperty *arrayProperty = nil;
        Class class = NULL;
        for (QSFetchModelProperty *property in [properties allValues]) {
            
            NSString *valueProtocol = [property.objectProtocols firstObject];
            class = NSClassFromString(valueProtocol);
            if ([valueProtocol isKindOfClass:[NSString class]] && [class isSubclassOfClass:[QSFetchModel class]]) {
                arrayProperty = property;
                break;
            }
        }
        
        if (arrayProperty && class) {
            id value = [(NSArray *)dataObject modelArrayWithClass:class];
            [self setValue:value forKey:arrayProperty.name];
        }
    }else if ([dataObject isKindOfClass:[NSDictionary class]]){
        
        for (QSFetchModelProperty *property in [properties allValues]) {
            
            NSString *jsonKey = property.name;
            NSString *mapperKey = [keyMapper objectForKey:jsonKey];
            jsonKey = mapperKey ?: jsonKey;
            
            id jsonValue = [dataObject objectForKey:jsonKey];
            id propertyValue = [self valueForProperty:property withJSONValue:jsonValue];
            
            if (propertyValue) {
                
                [self setValue:propertyValue forKey:property.name];
            }else{
                
                if (self.enableResetInjectData && jsonKey.length) {
                    id resetValue = (property.valueType == QSClassPropertyTypeObject) ? nil : @(0);
                    [self setValue:resetValue forKey:property.name];
                }
            }
        }
    }else if ([dataObject isKindOfClass:[NSNumber class]]){
        if ([NSStringFromClass([dataObject class]) hasSuffix:@"CFBoolean"]) {
            _bizResult = [dataObject boolValue];
        }
        else if ([NSStringFromClass([dataObject class]) hasSuffix:@"CFNumber"]){
            _dataCount = dataObject;
        }
    }
    else if ([dataObject isKindOfClass:[NSString class]]){
        _dataResut = dataObject;
    }
}

- (id)valueForProperty:(QSFetchModelProperty *)property withJSONValue:(id)value{
    
    id resultValue = value;
    if (value == nil || [value isKindOfClass:[NSNull class]]) {
        resultValue = nil;
    }else{
        if (property.valueType != QSClassPropertyTypeObject) {
            
            if ([value isKindOfClass:[NSString class]]) {
                if (property.valueType == QSClassPropertyTypeInt ||
                    property.valueType == QSClassPropertyTypeUnsignedInt||
                    property.valueType == QSClassPropertyTypeShort||
                    property.valueType == QSClassPropertyTypeUnsignedShort) {
                    resultValue = [NSNumber numberWithInt:[(NSString *)value intValue]];
                }
                if (property.valueType == QSClassPropertyTypeLong ||
                    property.valueType == QSClassPropertyTypeUnsignedLong ||
                    property.valueType == QSClassPropertyTypeLongLong ||
                    property.valueType == QSClassPropertyTypeUnsignedLongLong){
                    resultValue = [NSNumber numberWithLongLong:[(NSString *)value longLongValue]];
                }
                if (property.valueType == QSClassPropertyTypeFloat) {
                    resultValue = [NSNumber numberWithFloat:[(NSString *)value floatValue]];
                }
                if (property.valueType == QSClassPropertyTypeDouble) {
                    resultValue = [NSNumber numberWithDouble:[(NSString *)value doubleValue]];
                }
                if (property.valueType == QSClassPropertyTypeChar) {
                    //对于BOOL而言，@encode(BOOL) 为 c 也就是signed char
                    resultValue = [NSNumber numberWithBool:[(NSString *)value boolValue]];
                }
            }
        }else{
            Class valueClass = property.objectClass;
            if ([valueClass isSubclassOfClass:[QSFetchModel class]] &&
                [value isKindOfClass:[NSDictionary class]]) {
                resultValue = [[valueClass alloc] initWithJSONDict:value];
            }
            
            if ([valueClass isSubclassOfClass:[NSString class]] &&
                ![value isKindOfClass:[NSString class]]) {
                resultValue = [NSString stringWithFormat:@"%@",value];
            }
            
            if ([valueClass isSubclassOfClass:[NSNumber class]] &&
                [value isKindOfClass:[NSString class]]) {
                NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
                [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
                resultValue = [numberFormatter numberFromString:value];
            }
            
            NSString *valueProtocol = [property.objectProtocols lastObject];
            if ([valueProtocol isKindOfClass:[NSString class]]) {
                
                Class valueProtocolClass = NSClassFromString(valueProtocol);
                if (valueProtocolClass != nil) {
                    if ([valueProtocolClass isSubclassOfClass:[QSFetchModel class]]) {
                        //array of models
                        if ([value isKindOfClass:[NSArray class]]) {
                            resultValue = [(NSArray *)value modelArrayWithClass:valueProtocolClass];
                        }
                        //dictionary of models
                        if ([value isKindOfClass:[NSDictionary class]]) {
                            resultValue = [(NSDictionary *)value modelDictionaryWithClass:valueProtocolClass];
                        }
                    }
                }
            }
        }
    }
    return resultValue;
}

@end